﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace WJP.ObjectModel
{
    /// <summary>
    /// 一个可被冻结的对象
    /// </summary>
    public abstract class Freezable : Extendable, IFreezable
    {
        /// <summary>
        /// 当前的对象是否已经被冻结了
        /// <para>子类可以在对状态进行更新时检查此属性</para>
        /// </summary>
        public bool IsFrozen { get; private set; }

        /// <summary>
        /// 冻结对象，使对象变为不可变对象
        /// </summary>
        public void Freeze()
        {
            IsFrozen = true;
            OnFrozen();
        }

        /// <summary>
        /// 解冻对象，使对象变为可变对象
        /// </summary>
        public void UnFreeze()
        {
            IsFrozen = false;
            OnUnFrozen();
        }

        /// <summary>
        /// 调用此方法保证本对象还没有被冻结。否则会抛出异常
        /// </summary>
        protected void CheckUnFrozen()
        {
            if (IsFrozen)
                throw new InvalidOperationException("本对象已经被冻结，不可再改变".Translate());
        }

        /// <summary>
        /// 设置字段值，此方法会调用<see cref="CheckUnFrozen"/>检查字段是否被冻结
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="field"></param>
        /// <param name="value"></param>
        protected void SetValue<T>(ref T field, T value)
        {
            CheckUnFrozen();
            field = value;
        }

        /// <summary>
        /// 当对象被冻结时发生
        /// <para>对本对象所有字段执行冻结操作</para>
        /// </summary>
        protected virtual void OnFrozen()
        {
            foreach (FieldInfo enumerateAllFiled in EnumerateAllFileds())
            {
                if (!enumerateAllFiled.IsDefined<UnAutoFreezeAttribute>())
                {
                    object value = enumerateAllFiled.GetValue(this);
                    if (value is Freezable freezable1)
                    {
                        freezable1.Freeze();
                    }
                    else
                    {
                        if (value is IEnumerable enumerable)
                        {
                            foreach (object item in enumerable)
                            {
                                if (item is Freezable freezable2)
                                    freezable2.Freeze();
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 当对象解冻时发生
        /// <para>对本对象所有字段执行解冻操作</para>
        /// </summary>
        protected virtual void OnUnFrozen()
        {
            foreach (FieldInfo enumerateAllFiled in EnumerateAllFileds())
            {
                if (!enumerateAllFiled.IsDefined<UnAutoFreezeAttribute>())
                {
                    object value = enumerateAllFiled.GetValue(this);
                    if (value is Freezable freezable1)
                    {
                        freezable1.UnFreeze();
                    }
                    else
                    {
                        if (value is IEnumerable enumerable)
                        {
                            foreach (object item in enumerable)
                            {
                                if (item is Freezable freezable2)
                                    freezable2.UnFreeze();
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 获取对象所有的字段
        /// </summary>
        /// <returns></returns>
        private IEnumerable<FieldInfo> EnumerateAllFileds()
        {
            for (Type type = this.GetType(); type != typeof(Freezable) && type != null; type = type.BaseType)
            {
                FieldInfo[] fieldInfoArray = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
                for (int index = 0; index < fieldInfoArray.Length; ++index)
                {
                    FieldInfo field = fieldInfoArray[index];
                    yield return field;
                }
            }
        }

        /// <summary>
        /// 调用所有孩子的Freeze方法
        /// </summary>
        /// <param name="children"></param>
        protected static void FreezeChildren(IEnumerable children)
        {
            foreach (var child in children)
            {
                if (!(child is Freezable freezable))
                    throw new InvalidOperationException("集合内所有对象必须继承自Freeable".Translate());
                freezable.Freeze();
            }
        }

        /// <summary>
        /// 调用所有孩子的Freeze方法
        /// </summary>
        /// <param name="children"></param>
        protected static void FreezeChildren(params Freezable[] children)
        {
            foreach (Freezable child in children)
            {
                if (child == null)
                    throw new InvalidOperationException("集合内所有对象必须继承自Freeable。");
                child.Freeze();
            }
        }


        /// <summary>
        /// 复制对象
        /// </summary>
        /// <param name="option"></param>
        /// <returns></returns>
        public IFreezable Clone(IFreezableCloneOptions option)
        {
            if (!option.CopiedPairs.TryGetValue(this, out IFreezable freezable))
            {
                freezable = Activator.CreateInstance(GetType()) as IFreezable;
                option.CopiedPairs[this] = freezable;
                CloneValues(this, option);
            }
            return freezable;
        }

        /// <summary>
        /// 子类重写此方法来实现更复杂的复制功能
        /// <para>默认使用反射进行对象的复制</para>
        /// </summary>
        /// <param name="target"></param>
        /// <param name="option"></param>
        protected virtual void CloneValues(IFreezable target, IFreezableCloneOptions option)
        {
            IsFrozen = false;
            foreach (FieldInfo filed in EnumerateAllFileds())
            {
                if (filed.IsDefined<NonSerializedAttribute>())
                    continue;

                var value = filed.GetValue(target);
                if (value is IFreezable freezable && option.DeepCloneRef)
                    value = freezable.Clone(option);

                if (value is IList && option.CloneChildren)
                {
                    var childValue = filed.GetValue(this);
                    if (childValue != null)
                    {
                        if (!(childValue is IList list))
                            throw new NotSupportedException("不支持不能转换为 IList 的字段{0}".Translate().FormatArgs(filed.Name));
                        if (list.IsReadOnly)
                            continue;

                        foreach (var item in value as IEnumerable)
                        {
                            if (item is IFreezable freeza)
                            {
                                IFreezable free = freeza.Clone(option);
                                list.Add(free);
                            }
                        }
                    }
                }
                else
                {
                    filed.SetValue(this, value);
                }
            }
        }
    }
}